/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package drone;

import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.shape.Box;
import com.jme3.texture.Texture;
import java.util.Random;
import static drone.CarStatus.LEAVE;
import static drone.CarStatus.TOSTART;
import static drone.CarStatus.WAIT;

/**
 *
 * @author Robert Verginien Nickel
 */
public class Car extends Geometry{
      
    private AssetManager assetManager;
    private CarStatus status;
    private Coordinates gate;
    private Drone drone;
    private Coordinates dronePosition;
    private Coordinates position;
    private Random r;
    private Geometry car;
    private Texture carSecondTex;
    private Material carMat;
    private Barrier gateModel;
    private boolean isFirst;
    private boolean isParked;
    private Coordinates lastSeen;
    
    final float THRESHOULD = 0.1f;
    final float VELOCITY = 4.8f;
    final float SIGHT = 12f;
    final float STARTFOLLOWDIST = 2f;
    final float HEIGHT = 0.5f;
    final float WIDTH = 0.85f;
    final float LEAVINGPROB = 0.000002f;
    
    /**
     * 
     * @param assetManager 
     * @param drone has to know the drone, which it should follow
     * @param node
     * @param position where the new Car is, when created
     * @param gateModel because the Barrier opens depending on the car
     * @param isParked preinitialized cars require a true value, dynamically created cars require a false
     */
    public Car(AssetManager assetManager, Drone drone, Node node, Coordinates position, Barrier gateModel, boolean isParked) {
        super();
        //This sets the object variables to the parameters
        this.assetManager = assetManager;
        this.position = position;
        this.gateModel = gateModel;
        this.isParked = isParked;
        this.drone = drone;
        
        gate = drone.getGate();
        dronePosition = new Coordinates(drone.getDronePosition().getX(), drone.getDronePosition().getZ());
        r = new Random();
        setAppearance(node);
        
        //every car is not first on default
        isFirst = false;

        //this is the default status for every car, that is created in the runtime
        status = CarStatus.TOSTART;
        System.out.println("<<< New Car created, status: TOSTART >>>");
    }
    
    /**
     * does all the Appearance stuff for the Car which are
     * - random color
     * - texture
     * - material
     * 
     * @param node 
     */
    private void setAppearance(Node node) {                
        Box boxMesh = new Box(WIDTH,HEIGHT,WIDTH);
        car = new Geometry("geometry", boxMesh);
        car.setMesh(boxMesh);
        
        carMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); 
        
        ColorRGBA color = new ColorRGBA(r.nextFloat(), r.nextFloat(), r.nextFloat(), 1);
        carMat.setColor("Color", color);

        car.setMaterial(carMat);
        car.setLocalTranslation(position.getxPos(), 0.5f, position.getzPos());
        
        node.attachChild(car);
    }
    
    /**
     * this is the most important method in the car
     * it is called with every drawn frame (about 200 times-per-second)
     * we call it the "State Machine", because it handles the status in frame,
     * differentiates the recent status and makes the needed actions for the car,
     * which are the following:
     * 
     * The car..
     * - TOSTART: ..is driving to the gate from outside of the parkinglot
     * - WAIT: ..is waiting at the gate for the drone to be lead to the parking-space
     * - FOLLOW: ..follows the drone to the assigned parking space
     * - PARKING: ..is parking the car (driving to the middle of the parking lot, active)
     * - PARK: ..is standing there (passive)
     * - LOST: ..lost the drone and doesn't know what to do
     * - LEAVE: ..drives away (okay, actually it just drops through the ground :D)
     * 
     * @param tpf 
     */
    public void update(float tpf) {
        //getting the correct Position of the Drone
        dronePosition.setxPos(drone.getDronePosition().getX());
        dronePosition.setzPos(drone.getDronePosition().getZ());
        switch(status){
            case TOSTART :    
                if(distanceBetween(gate, position) > THRESHOULD){
                    driveTo(gate, tpf);
                }
                
                else{
                    //arrived at the Gate
                    status = CarStatus.WAIT;
                    Controller.getInstance(drone).submitTask();
                    gateModel.setStatus(BarrierStatus.OPENING);
                }
            break;
            case WAIT :
                if(drone.isDroneGuiding() && isFirst()){
                    //the drone came and wants this car to follow
                    System.out.println("<<< Status changed to: FOLLOW >>>");
                    status = CarStatus.FOLLOW;
                }
            break;
            case FOLLOW :
                    isFirst = false;
                    //following the drone, if it is within sight & it wants to be followed
                    if(drone.isDroneGuiding() && distanceBetween(dronePosition, position) < SIGHT){
                        driveTo(dronePosition, tpf);
                        lastSeen = new Coordinates(dronePosition.getxPos(), dronePosition.getzPos());
                    }
                    else{
                        if(drone.isDroneGuiding() && distanceBetween(dronePosition, position) > SIGHT){
                            //car is lost, because the drone wants me to follow, but i cant see it anymore
                            System.out.println("<<< Status is changes to: LOST");
                            status = CarStatus.LOST; 
                        }
                        else{
                            //the drone wants the car to park at this place
                            System.out.println("<<< Status is changed to: PARKING >>>");
                            status = CarStatus.PARKING;              
                        }
                    }
            break;
            case PARKING :
                //parks the car at the last position, where the drone send the signal to follow
                //this prevents the former 'jumps' from the road to the space
                if(distanceBetween(position, lastSeen) > THRESHOULD){
                    driveTo(lastSeen, tpf);
                }
                else{
                    System.out.println("<<< Status is changed to: PARK >>>");
                    status = CarStatus.PARK;
                }
                break;
            case PARK :
                isParked = true;
                //drive-out is calculated randomly
                if(r.nextDouble() < LEAVINGPROB){
                    System.out.println("<<< Status changed to: LEAVE>>>");
                    status = CarStatus.LEAVE;
                    DataBaseSpaces.getInstance().findClosestSpace(position).setEmpty(true);
                }    
            break;
            case LOST :
                //it could be possible at this point, to let the drone fly slower
                //just before the car is totally lost
                lostCar();
            break;
            case LEAVE :
                descend(tpf);
            break;
        }
    }
    /**
     * lets the car drop through the ground
     * to simulate that the car drives away
     * @param time-per-frame to make a fluent, fps-independent movement
     */
    private void descend(float tpf){
        if(car.getLocalTranslation().getY() > -0.51f){
            car.move(0, -1f * VELOCITY * tpf, 0);
        }
    }
    /**
     * 
     * @param target-coordinates, where the car should go
     * @param time-per-frame to make a fluent, fps-independent movement
     */
    private void driveTo(Coordinates c, Float tpf){
        Vector3f vectorToMove;
        Vector3f normVector;
        float movementX = c.getxPos() - position.getxPos();
        float movementY = 0;
        float movementZ = c.getzPos() - position.getzPos();
        
        float vectorLength = (float) Math.sqrt(Math.pow(movementX,2) + Math.pow(movementY,2) + Math.pow(movementZ,2));
        normVector = new Vector3f(movementX/vectorLength, movementY/vectorLength, movementZ/vectorLength);
        
        //this is the final vector (depending on the time-per-frame and velocity)
        vectorToMove = new Vector3f(normVector.getX() * VELOCITY * tpf, 0, normVector.getZ() * VELOCITY * tpf);
        
        position.setxPos(car.getLocalTranslation().getX());
        position.setzPos(car.getLocalTranslation().getZ());
        
        car.move(vectorToMove);
    }
    /**
     * 
     * @param coordsA
     * @param coordsB
     * @return the distance between the two Coordinate-parameters
     */
    private float distanceBetween(Coordinates coordsA, Coordinates coordsB){
        float a = Math.abs(coordsA.getxPos() - coordsB.getxPos());
        float b = Math.abs(coordsA.getzPos() - coordsB.getzPos());
        float result = (float) Math.sqrt(Math.pow(a, 2)+Math.pow(b, 2));
        return result;
    }
    /**
     * Sets the Questionmark on the car, if it is lost
     */
    private void lostCar() {
        carSecondTex = assetManager.loadTexture("Interface/lost.png"); 
        carMat.setTexture("ColorMap", carSecondTex); 
        car.setMaterial(carMat);
    }
    /**
     * default setter-Method for the status of the Car
     * @param status
     */
    public void setStatus(CarStatus status){
        this.status = status;
    }
    /**
     * 
     * @return true, if the car is the next one to follow the drone, else false
     */
    public boolean isFirst(){
        return isFirst;
    }
    /**
     * 
     * @return true, if the car is already parked (not in the queue of the waiting cars anymore)
     */
    public boolean isParked(){
        return isParked;
    }
    /**
     * 
     * @param isFirst true, to make the car the next one to follow the drone
     */
    public void setFirst(boolean isFirst){
        this.isFirst = isFirst;
    }
}